        # 
        # file: trampoline.S
        # date: 23.04.27
        # author: xin
        # brief: 从用户空间进入内核处理trap和从内核返回用户空间的汇编代码
        # version: 0.1
        # status: Stable
        # 
        # 内核将保存该代码的页映射到用户空间与内核空间相同的虚拟地址（TRAMPOLINE，**暂未启用**）
        # kernel.ld使得该代码从页边界开始
        # 

#include "riscv.h"
#include "memlayout.h"

.section trampsec
.globl trampoline
trampoline:
.align 4
.globl uservec
uservec:
        # 
        # trap.c会将stvec值指向此处，所以从用户空间trap后
        # 会从此处开始，即便处于S模式，也是使用的用户页表
        # 

        # 保存user的a0到sscratch中
        # a0就可以用来保存TRAPFRAME
        csrw sscratch, a0

        # 每个进程都有一个单独的p->trapframe内存区域
        # 但都映射向每个进程的相同的虚拟地址页表（TRAPFRAME）
        li a0, TRAPFRAME

        # 在TRAPFRAM中暂存用户的寄存器
        sd ra, 40(a0)
        sd sp, 48(a0)
        sd gp, 56(a0)
        sd tp, 64(a0)
        sd t0, 72(a0)
        sd t1, 80(a0)
        sd t2, 88(a0)
        sd s0, 96(a0)
        sd s1, 104(a0)
        sd a1, 120(a0)
        sd a2, 128(a0)
        sd a3, 136(a0)
        sd a4, 144(a0)
        sd a5, 152(a0)
        sd a6, 160(a0)
        sd a7, 168(a0)
        sd s2, 176(a0)
        sd s3, 184(a0)
        sd s4, 192(a0)
        sd s5, 200(a0)
        sd s6, 208(a0)
        sd s7, 216(a0)
        sd s8, 224(a0)
        sd s9, 232(a0)
        sd s10, 240(a0)
        sd s11, 248(a0)
        sd t3, 256(a0)
        sd t4, 264(a0)
        sd t5, 272(a0)
        sd t6, 280(a0)

        # 从sscratch中取回a0
        # 存到p->trapframe->a0中
        csrr t0, sscratch
        sd t0, 112(a0)

        # 用p->trapframe->kernel_sp初始化内核栈指针
        ld sp, 8(a0)

        # 用p->trapframe->kernel_hartid赋值tp
        ld tp, 32(a0)

        # 取出p->trapframe->kernel_trap取出usertrap()地址
        ld t0, 16(a0)

        # 从p->trapframe->kernel_satp取出内核页表地址
        ld t1, 0(a0)

        # 等待任何先前的内存操作完成
        # 以便后续使用user页表
        sfence.vma zero, zero

        # 将p->trapframe->kernel_satp赋值给当前的寄存器satp
        csrw satp, t1

        # 再次刷新TLB中已经过期的user项
        sfence.vma zero, zero

        # 不带返回的跳转p->trapframe->kernel_trap（usertrap()）
        jr t0

.globl userret
userret:
        # 由trap.c::usertrapret()调用
        # 用来从内核转到user
        
        # 转到user页表
        # a0：存储着用户页表
        sfence.vma zero, zero
        csrw satp, a0
        sfence.vma zero, zero

        li a0, TRAPFRAME

        # 将TRAPFRAME中所有user寄存器取出（a0除外）
        ld ra, 40(a0)
        ld sp, 48(a0)
        ld gp, 56(a0)
        ld tp, 64(a0)
        ld t0, 72(a0)
        ld t1, 80(a0)
        ld t2, 88(a0)
        ld s0, 96(a0)
        ld s1, 104(a0)
        ld a1, 120(a0)
        ld a2, 128(a0)
        ld a3, 136(a0)
        ld a4, 144(a0)
        ld a5, 152(a0)
        ld a6, 160(a0)
        ld a7, 168(a0)
        ld s2, 176(a0)
        ld s3, 184(a0)
        ld s4, 192(a0)
        ld s5, 200(a0)
        ld s6, 208(a0)
        ld s7, 216(a0)
        ld s8, 224(a0)
        ld s9, 232(a0)
        ld s10, 240(a0)
        ld s11, 248(a0)
        ld t3, 256(a0)
        ld t4, 264(a0)
        ld t5, 272(a0)
        ld t6, 280(a0)

	# 取出user的a0
        ld a0, 112(a0)

        # 返回user模式以及user pc
        # usertrapret()中已经设置了sstatus和sepc
        sret
